/**
 * @fileoverview GameJs is a port of the [PyGame](http://pygame.org) API to
 * JavaScript, using the Canvas provided by modern browsers.
 *
 *  * [Download, Project Page](http://code.google.com/p/gamejs/)
 *   * [API Documentation](/apidoc/)
 *  * [Tyround Example Game](/tyround/)
 *  * [.. browse Examples](/examples/)
 *
 * **Graphics**
 *
 * GameJs provides utilities to create a Display for 2D Graphics. This display
 * can be accessed as a `gamejs.Surface` and other Surfaces can be blitted 
 * (copied) onto it. Surfaces can be created by loading pixel images or
 * by drawing onto empty Surfaces with the `gamejs.draw` methods.
 *
 * **Event System**
 *
 * GameJs is event loop based. You will typically have a main loop or a main function
 * called by interval. Input from mouse & keyboard can be pooled with methods
 * from `gamejs.event`.
 *
 * For managing ticks so that the game speed is independent of the actual framerate,
 * `gamejs.time` provides tools for creating interval callbacks.
 *
 * This top module holds the important `gamejs.Rect` and `gamejs.Surface` classes.
 */
 
/**
 * Initialize all subsystems. Necessary before any graphics or sounds
 * can be output.
 * @ignore
 * @private
 */
exports.init = function() {
   if (INITIALIZED === true) return true;
   // FIXME only init the modules which were loaded and hint dependencies
   gamejs.time.init();
   gamejs.display.init();
   gamejs.image.init();
   gamejs.mixer.init();
   gamejs.event.init();
   INITIALIZED = true;
   return INITIALIZED;
}

var ERROR = 0;
var WARNING = 1;
var DEBUG = 2;

var DEBUG_LEVEL = 2;
var DEBUG_LEVELS = ['error', 'warning', 'debug'];

var PRELOADING = true;
var INITIALIZED = false;

/**
 * Log a msg to the console if console is enable
 * @param {String} msg the msg to log
 */
exports.log = function(msg, lvl) {
   //if (lvl === undefined || DEBUG_LEVEL <= parseInt(lvl)) {
      if (window.console !== undefined) console.log(msg);      
   //} 
};

/**
 * Creates a Rect. Rects are used to hold rectangular areas. There are a couple
 * of convinient ways to create Rects with different arguments and defaults.
 * A Rect has descriptive fields <code>left, right, top, bottom, width, height</code>
 * which can all be assigned to.
 *
 * @example
 * new Rect([left, top]) width & height default to 0
 * new Rect(left, top) width & height default to 0
 * new Rect(left, top, width, height)
 * new Rect([left, top], [width, height])
 * 
 * @propert {Number} left
 * @propert {Number} right
 * @propert {Number} top
 * @propert {Number} bottom
 * @propert {Number} bottom
 * 
 * @constructor
 * @class Rects are used a lot. They are good for collision detection, specifying
 *        an area on the screen (for blitting) or just to hold an objects position.
 *
 * @param {Number[]} position Array holding left and top coordinates
 * @param {Number[]} dimensions Array holding width and height
 */
exports.Rect = Rect = function() {
   this.left = 0;
   this.top = 0;
   this.width = 0;
   this.height = 0;
   
   if (arguments.length === 2) {
      if (arguments[0] instanceof Array && arguments[1] instanceof Array) {   
         this.left = arguments[0][0];
         this.top = arguments[0][1];
         this.width = arguments[1][0];
         this.height = arguments[1][1];
      } else {
         this.left = arguments[0];
         this.top = arguments[1];
      }
   } else if (arguments.length === 1 && arguments[0] instanceof Array) {
      this.left = arguments[0][0];
      this.top = arguments[0][1];
      this.width = 0;
      this.height = 0;
   } else if (arguments.length === 4) {
      this.left = arguments[0];
      this.top = arguments[1];
      this.width = arguments[2];
      this.height = arguments[3];
   }
   
   return this;
};

Rect.prototype.__defineGetter__("bottom", function() {
   return this.top + this.height;
});
Rect.prototype.__defineGetter__("right", function() {
   return this.left + this.width;
});
Rect.prototype.__defineGetter__("center", function() {
   return [parseInt(this.left + this.width / 2), parseInt(this.top + this.height / 2)];
});
Rect.prototype.__defineSetter__("center", function(center) {
   if (!typeof(center) === 'array' || center.length < 2) {
      throw new Error('tried to set center of ' + this + ' to non array' + center);
   }
   this.left = parseInt(center[0] - this.width / 2);
   this.top = parseInt(center[1] - this.height / 2);
   return;
});

/**
 * Move returns a new Rect, which is a version of this Rect
 * moved by the given amounts.
 * @param {Number} x amount to move on x axis
 * @param {Number} y amount to move on y axis
 */
Rect.prototype.move = function(x, y) {
   return new gamejs.Rect(this.left+x, this.top+y, this.width, this.height);
};

/**
 * Move this Rect in place - not returning a new Rect like `move(x, y)`.
 *
 * `moveIp(x,y)` or `moveIp([x,y])`
 *
 * @param {Number} x amount to move on x axis
 * @param {Number} y amount to move on y axis
 */
Rect.prototype.moveIp = function(x, y) {
   if (arguments.length == 1) {
      x = arguments[0][0];
      y = arguments[0][1];
   }
   this.left += x;
   this.top += y;
};

/**
 * Check for collision with a point.
 *
 * `collidePoint(x,y)` or `collidePoint([x,y])` or `collidePoint(new Rect(x,y))`
 *
 *
 * @param {Number[]} point the x and y coordinates of the point to test for collision
 * @returns {Boolean} true if the point collides with this Rect
 */
Rect.prototype.collidePoint = function() {
   if (arguments.length == 1 && arguments[0] instanceof Rect) {
      x = arguments[0].left;
      y = arguments[0].top;
   } else if (arguments.length == 1) {
      x = arguments[0][0];
      y = arguments[0][1];
   } else {
      x = arguments[0];
      y = arguments[1];
   }
   return (this.left < x && x < this.right) &&
       (this.top < y && y < this.bottom)   
};

/**
 * Check for collision with a Rect.
 * @param {gamejs.Rect} rect the Rect to test check for collision
 * @returns {Boolean} true if the given Rect collides with this Rect
 */   
Rect.prototype.collideRect = function(rect) {
   return !(this.left > rect.right || this.right < rect.left ||
      this.top > rect.bottom || this.bottom < rect.top);
};

Rect.prototype.toString = function() {
   return ["[", this.left, ",", this.top, "]"," [",this.width, ",", this.height, "]"].join("");
}

/**
 * Creates a Surface. A Surface represents an image with a fixed width and height.
 * Surfaces can be <code>blit()</code>ed (copied) onto other Surfaces.
 * FIXME add constructor (width, height)
 *
 * @constructor
 *
 * @param {Number[]} dimensions Array holding width and height
 */
exports.Surface = Surface = function(dims) {
   if (arguments.length == 2) {
      dims = [arguments[0], arguments[1]];
   } else if (arguments.length == 1 && dims instanceof gamejs.Rect) {   
      dims = [dims.width, dims.height];
   }
   var width = dims[0];
   var height = dims[1];
   // only for rotatation & scale
   this._matrix = gamejs.matrix.identity();
	this._canvas = document.createElement("canvas");
	this._canvas.width = width;
	this._canvas.height = height;
   return this;
};

/**
 * Blits another Surface on this Surface. The destination where to blit to
 * can be given (or it defaults to the top left corner) as well as the
 * Area from the Surface which should be blitted (e.g., for cutting out parts of
 * a Surface).
 *
 * @param {gamejs.Surface} src The Surface which will be blitted onto this one
 * @param {gamejs.Rect|Number[]} [dst] the Destination x, y position in this Surface.
 *            If a Rect is given, it's top and left values are taken. If this argument
 *            is not supplied the blit happens at [0,0].
 * @param {gamesjs.Rect|Number[]} [area] the Area from the passed Surface which
 *            should be blitted onto this Surface.
 * @param {Number} [special_flags] FIXME add special flags for blit params
 */
Surface.prototype.blit = function(src, dest, area, special_flags) {
   var rDest, rArea;
   
   // dest, we only care about x, y
   if (dest instanceof gamejs.Rect) {
      rDest = new gamejs.Rect([dest.left, dest.top], src.getSize());
   } else if (dest && dest instanceof Array && dest.length == 2) {
      rDest = new gamejs.Rect(dest, src.getSize());
   } else {
      rDest = new gamejs.Rect([0,0], src.getSize());
   }
   
   // area within src to be drawn
   if (area instanceof gamejs.Rect) {
      rArea = area;
   } else if (area && area instanceof Array && area.length == 2) {
      rArea = new gamejs.Rect(area, src.getSize());
   } else {
      rArea = new gamejs.Rect([0,0], src.getSize());
   }

   if (isNaN(rDest.left) || isNaN(rDest.top) || isNaN(rDest.width) || isNaN(rDest.height)) {
      throw new Error('[blit] bad parameters, destination is ' + rDest);
   }
   
   this.context.save();
   // first translate, then rotate
   var m = gamejs.matrix.translate(gamejs.matrix.identity(), rDest.left, rDest.top);
   m = gamejs.matrix.multiply(m, src._matrix);
   this.context.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
   srcRect = src.getRect();
   this.context.drawImage(src.canvas, rArea.left, rArea.top, srcRect.width, srcRect.height, 0, 0, srcRect.width, srcRect.height)
   this.context.restore();
   return;
};

/**
 * @returns {Number[]} the width and height of the Surface
 */
Surface.prototype.getSize = function() {
   return [this.canvas.width, this.canvas.height];
};

/**
 * @returns {gamejs.Rect} a Rect of the size of this Surface
 */
Surface.prototype.getRect = function() {
   return new gamejs.Rect([0,0], this.getSize());
};

/**
 * Fills the whole Surface with a color. Usefull for erasing a Surface.
 * @param {String} color an #RGB string, e.g., #00ff00
 */
// rgb css color
Surface.prototype.fill = function(color) {
   this.context.save();
   this.context.fillStyle = color || "#000000";
   this.context.fillRect(0, 0, this.canvas.width, this.canvas.height);
   this.context.restore();
   return;
};


Surface.prototype.__defineGetter__("rect", function() {
   return this.getRect();
});

/**
 * @ignore
 */
Surface.prototype.__defineGetter__('context', function() { 
   return this._canvas.getContext('2d');
});

/**
 * @ignore
 */
Surface.prototype.__defineGetter__('canvas', function() {
   return this._canvas;
});
